package engine

import (
	"gateway/config"
	"gateway/connector"
	"gateway/handler"
	"gateway/inbound"
	"gateway/outbound"
	"go.uber.org/zap"
)

func createConnectors(runtime *engineRuntime, cfg *config.RoutesConfig) {
	for _, c := range cfg.ConnectorsConfig {
		runtime.connectorsRuntime[c.Id] = createConnector(&c)
	}
}

func createConnector(c *config.ConnectorConfig) connectorRuntime {
	conn := connector.NewConnector(c.Name)
	conn.SetArgs(c.Args)
	return connectorRuntime{
		id:        c.Id,
		connector: conn,
	}
}

func createInbounds(runtime *engineRuntime, cfg *config.RoutesConfig) {
	for _, c := range cfg.InboundsConfig {
		runtime.inboundsRuntime[c.Id] = createInbound(&c)
	}
}

func createInbound(c *config.InboundConfig) inboundRuntime {
	in := inbound.NewInbound(c.Name)
	in.SetArgs(c.Args)
	return inboundRuntime{
		id:      c.Id,
		inbound: in,
	}
}

func createOutbounds(runtime *engineRuntime, cfg *config.RoutesConfig) {
	for _, c := range cfg.OutboundsConfig {
		runtime.outboundsRuntime[c.Id] = createOutbound(&c)
	}
}

func createOutbound(c *config.OutboundConfig) outboundRuntime {
	out := outbound.NewOutbound(c.Name)
	out.SetArgs(c.Args)
	return outboundRuntime{
		id:       c.Id,
		outbound: out,
	}
}

func createRoutes(runtime *engineRuntime, cfg *config.RoutesConfig) {
	for _, c := range cfg.RoutesConfig {
		route := newRouteRuntime()

		_, ok := runtime.connectorsRuntime[c.ConnectorConfig.Id]
		if !ok {
			config.Logger.Panic("Connector is not exist", zap.String("id", c.ConnectorConfig.Id))
		}
		route.id = c.Id

		in, ok := runtime.inboundsRuntime[c.InboundConfig.Id]
		if !ok {
			in = createInbound(&c.InboundConfig)
		}
		route.inboundRuntime = in

		out, ok := runtime.outboundsRuntime[c.OutboundConfig.Id]
		if !ok {
			out = createOutbound(&c.OutboundConfig)
		}
		route.outboundRuntime = out

		handlerIds := make([]string, 0)
		for _, h := range c.HandlersConfig {
			hand := createHandler(&h)
			route.handlersRuntime = append(route.handlersRuntime, hand)
			handlerIds = append(handlerIds, h.Name)
		}

		routes, ok := runtime.routesRuntime[c.ConnectorConfig.Id]
		if !ok {
			routes = make([]routeRuntime, 0)
		}

		config.Logger.Info("Initialize Route",
			zap.String("id", route.id),
			zap.String("inbound", route.inboundRuntime.id),
			zap.Strings("handlers", handlerIds),
			zap.String("outbound", route.outboundRuntime.id))
		runtime.routesRuntime[c.ConnectorConfig.Id] = append(routes, *route)
	}
}

func createHandler(c *config.HandlerConfig) handlerRuntime {
	hand := handler.NewHandler(c.Name)
	hand.SetArgs(c.Args)
	return handlerRuntime{
		id:      c.Id,
		handler: hand,
	}
}

func initConnectors(runtime *engineRuntime) {
	for k, v := range runtime.routesRuntime {
		ins := make([]inboundRuntime, 0)
		for _, in := range v {
			ins = append(ins, in.inboundRuntime)
		}
		chain := newInboundChain(ins)
		runtime.connectorChains[k] = chain
		conn := runtime.connectorsRuntime[k]
		go conn.connector.Initialize(chain)
	}
}

func initRoutes(runtime *engineRuntime) {
	for _, rs := range runtime.routesRuntime {
		for _, r := range rs {
			for _, h := range r.handlersRuntime {
				h.handler.Initialize()
			}
			r.outboundRuntime.outbound.Initialize()
			chain := newRouteChain(r.handlersRuntime, r.outboundRuntime)
			r.routeChain = chain
			go r.inboundRuntime.inbound.Initialize(chain)
		}
	}
}

func destroyConnectors(connectors map[string]connectorRuntime) {
	for _, v := range connectors {
		v.connector.Destroy()
	}
}

func destroyInbounds(inbounds map[string]inboundRuntime) {
	for _, v := range inbounds {
		v.inbound.Destroy()
	}
}

func destroyOutbounds(outbounds map[string]outboundRuntime) {
	for _, v := range outbounds {
		v.outbound.Destroy()
	}
}

func destroyRoutes(run *engineRuntime) {
	for _, r := range run.routesRuntime {
		for _, route := range r {
			_, ok := run.inboundsRuntime[route.inboundRuntime.id]
			if !ok {
				route.inboundRuntime.inbound.Destroy()
			}
			_, ok = run.outboundsRuntime[route.outboundRuntime.id]
			if !ok {
				route.outboundRuntime.outbound.Destroy()
			}
			for _, h := range route.handlersRuntime {
				h.handler.Destroy()
			}
		}

	}
}
